package com.zhouchj.elasticsearch.basic;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * es func字段处理
 *
 * @author zhouchj
 * @date 2023/8/24
 */
@Slf4j
public class EsFieldProcessor<T> {

    /**
     * 缓存字段名
     */
    private static final Map<Class<?>, SerializedLambda> CLASS_LAMBDA_CACHE = new ConcurrentHashMap<>();

    /**
     * 当前索引的实体类class
     */
    protected final Class<T> clazz;

    protected EsFieldProcessor(Class<T> clazz) {
        this.clazz = clazz;
    }

    /**
     * 获取嵌套中的ES字段名
     *
     * @param nestedFunc 嵌套字段
     * @param subFunc    子字段名
     * @return 嵌套中的字段ES字段名
     * @throws RuntimeException 当嵌套字段中不存在@Field标识或type不等于{@link FieldType#Nested}
     */
    protected String getSubEsFieldName(SFunction<T, ?> nestedFunc, SFunction<?, ?> subFunc) {
        String methodFieldName = this.getMethodFieldName(nestedFunc);
        java.lang.reflect.Field field = ReflectUtil.getField(clazz, methodFieldName);
        Field annotation = field.getAnnotation(Field.class);
        if (Objects.isNull(annotation) || !FieldType.Nested.equals(annotation.type())) {
            throw new RuntimeException("嵌套字段未用@Field标识或type不等于FieldType.Nested");
        }
        String childMethodFieldName = this.getMethodFieldName(subFunc);
        Class<?> childClazz;
        if (field.getType().isAssignableFrom(List.class)) {
            // 获取List中的泛型
            Type generic = ((ParameterizedTypeImpl) field.getGenericType()).getActualTypeArguments()[0];
            try {
                childClazz = Class.forName(generic.getTypeName());
            } catch (ClassNotFoundException e) {
                throw new RuntimeException("未找到嵌套类的Class");
            }
        } else {
            childClazz = field.getType();
        }
        return this.convertToEsFieldName(childClazz, childMethodFieldName);
    }

    /**
     * 转换方法引用Function为属性名
     * 如果字段上有@Field注解 优先使用注解中的值
     * 如果没有,则使用字段名下划线样式 如字段imgName 返回则为img_name
     *
     * @param currentClazz fn所代表的的字段所在的类
     * @param fn           字段
     * @return es 字段名
     */
    protected String convertToEsFieldName(Class<?> currentClazz, SFunction<?, ?> fn) {
        String methodFieldName = this.getMethodFieldName(fn);
        return convertToEsFieldName(currentClazz, methodFieldName);
    }

    /**
     * 是否是嵌套字段
     *
     * @param fn 字段
     * @return boolean
     */
    protected boolean isNestField(SFunction<?, ?> fn) {
        String methodFieldName = this.getMethodFieldName(fn);
        try {
            java.lang.reflect.Field field = ReflectUtil.getField(clazz, methodFieldName);
            if (field.isAnnotationPresent(Field.class) && FieldType.Nested.equals(field.getAnnotation(Field.class).type())) {
                return true;
            }
        } catch (Exception ignored) {
        }
        return false;
    }

    /**
     * 是否是嵌套字段
     *
     * @param field 字段
     * @return boolean
     */
    protected boolean isId(java.lang.reflect.Field field) {
        return field.isAnnotationPresent(Id.class);
    }

    /**
     * 转换方法引用Function为属性名
     * 如果字段上有@Field注解 优先使用注解中的值
     * 如果没有,则使用字段名下划线样式 如字段imgName 返回则为img_name
     *
     * @param currentClazz    字段所在的类
     * @param methodFieldName 字段名
     * @return es 字段名
     */
    protected String convertToEsFieldName(Class<?> currentClazz, String methodFieldName) {
        java.lang.reflect.Field field = ReflectUtil.getField(currentClazz, methodFieldName);
        if (Objects.isNull(field)) {
            throw new RuntimeException(currentClazz.getName() + "类中找不到字段" + methodFieldName);
        }
        return convertToEsFieldName(field);
    }

    /**
     * 转换Field为ES字段
     * 如果字段上有@Field注解 优先使用注解中的值
     * 如果没有,则使用字段名下划线样式 如字段imgName 返回则为img_name
     *
     * @param field field
     * @return es字段名
     */
    protected String convertToEsFieldName(java.lang.reflect.Field field) {
        Field annotation = field.getAnnotation(Field.class);
        if (Objects.isNull(annotation) || StringUtils.isBlank(annotation.name())) {
            return CharSequenceUtil.toUnderlineCase(field.getName());
        }
        return annotation.name();
    }

    /**
     * 获取实体类中的字段名
     *
     * @param fn 字段
     * @return 字段名
     */
    private String getMethodFieldName(SFunction<?, ?> fn) {
        SerializedLambda lambda = this.getSerializedLambda(fn);
        String methodName = lambda.getImplMethodName();
        String prefix = null;
        if (methodName.startsWith("get")) {
            prefix = "get";
        } else if (methodName.startsWith("is")) {
            prefix = "is";
        }
        if (prefix == null) {
            throw new RuntimeException("类" + clazz.getName() + "中找不到改getter方法" + methodName);
        }
        return CharSequenceUtil.lowerFirst(methodName.substring(prefix.length()));
    }

    /**
     * 获取SerializedLambda
     *
     * @param fn 字段
     * @return 序列化Lambda
     */
    private SerializedLambda getSerializedLambda(Serializable fn) {
        SerializedLambda lambda = CLASS_LAMBDA_CACHE.get(fn.getClass());
        if (lambda == null) {
            try {
                Method method = fn.getClass().getDeclaredMethod("writeReplace");
                method.setAccessible(Boolean.TRUE);
                lambda = (SerializedLambda) method.invoke(fn);
                CLASS_LAMBDA_CACHE.put(fn.getClass(), lambda);
            } catch (Exception e) {
                throw new RuntimeException("反射获取method名称失败", e);
            }
        }
        return lambda;
    }
}
